class TypedVar:
    def __init__(self, varname, vartype):
        self.varname = varname
        self.vartype = vartype

# Snippets
class ShaderSnippet:
    def __init__(self, snip_dict, defines):
        self.name = snip_dict['name']
        self.code = ShaderSnippet.emit_code(snip_dict['snippets'], defines)

    @staticmethod
    def eval_cond(cond, defines):
        try:
            return False != eval(cond, defines)
        except:
            return False

    @staticmethod
    def emit_code(code_snips, defines):
        filtered_code_snippets = filter(lambda x: not x.has_key('cond') or ShaderSnippet.eval_cond(x['cond'], defines), code_snips)
        if len(filtered_code_snippets) > 1:
            return reduce(lambda x, y: lstrip(x['code']) + lstrip(y['code']), filtered_code_snippets)
        else:
            return '' if 0 == len(filtered_code_snippets) else filtered_code_snippets[0]['code'].lstrip()

    @classmethod
    def from_file(klass, file_name, defines = {}):
        file = open(file_name)
        try:
            snip_dict = eval(file.read())[0]
            return klass.from_dict(snip_dict, defines)
        finally:
            file.close()

class VertexShaderSnippet(ShaderSnippet):
    def __init__(self, snip_dict, defines):
        ShaderSnippet.__init__(self, snip_dict, defines)
        self.set_vertex(snip_dict)
    
    @staticmethod
    def from_dict(snip_dict, defines):
        return VertexShaderSnippet(snip_dict, defines)

    def set_vertex(self, snip_dict):
        self.vertex = {}
        for vt in snip_dict['vertex']:
            self.vertex[vt] = snip_dict[vt]

class PixelShaderSnippet(ShaderSnippet):
    def __init__(self, snip_dict, defines):
        ShaderSnippet.__init__(self, snip_dict, defines)
        self.set_samplers(snip_dict)
        self.set_interpolators(snip_dict)
    
    @staticmethod
    def from_dict(snip_dict, defines):
        return PixelShaderSnippet(snip_dict, defines)

    def set_samplers(self, snip_dict):
        self.samplers = snip_dict['textures']

    def set_interpolators(self, snip_dict):
        self.interpolators = map(lambda x: TypedVar(x, snip_dict[x]), snip_dict['interpolators'])

# Combiners
class SnippetCombiner:
    def make_name(self, name, index):
        return name + str(index)
        
    def emit_text(self, text):
        print text

class PixelSnippetCombiner(SnippetCombiner):
    def __init__(self):
        self.used_color_interpolators = 0
        self.used_texture_interpolators = 0
        
    def combine(self, snips):
        self.emit_textures(snips)
        self.emit_input(snips)
        self.emit_output(snips)
        self.emit_snippets(snips)
        self.emit_entrypoint(snips)

    def emit_textures(self, snips):
        self.emit_text('// -------- texture samplers --------')
        for i, snip in enumerate(snips):
            combined_snip_name = self.make_name(snip.name, str(i))
            self.emit_text('// %s textures' % (combined_snip_name))
            for samp in snip.samplers:
                self.emit_text('sampler %s_%s;' % (combined_snip_name, samp))
            self.emit_text('')

    def emit_input(self, snips):
        self.emit_text('// -------- input structures --------')
        # an input structure for each snippet ...
        for i, snip in enumerate(snips):
            combined_snip_name = self.make_name(snip.name, str(i))
            self.emit_text('struct %s_INPUT\n{' % (combined_snip_name))
            for interp in snip.interpolators:
                use_color_interpolator = 'color' == interp.vartype
                if use_color_interpolator:
                    self.emit_text('    %s %s : COLOR%d;' % (interp.vartype, interp.varname, self.used_color_interpolators))
                    self.used_color_interpolators += 1
                else:
                    self.emit_text('    %s %s : TEXCOORD%d;' % (interp.vartype, interp.varname, self.used_texture_interpolators))
                    self.used_texture_interpolators += 1
            self.emit_text('};\n')

        # ... and a combined input structure
        self.emit_text('struct INPUT\n{')
        for i, snip in enumerate(snips):
            combined_snip_name = self.make_name(snip.name, str(i))
            self.emit_text('    ' + combined_snip_name + '_INPUT ' + combined_snip_name + ';')
        self.emit_text('};\n')
        self.emit_text('INPUT gInput;\n')

    def emit_output(self, snips):
        self.emit_text("""// -------- output type --------
struct OUTPUT
{
    float4 color : COLOR;""")
        # and there might be other things to emit here
        self.emit_text('};\n')

    def emit_snippets(self, snips):
        for i, snip in enumerate(snips):
            combined_snip_name = self.make_name(snip.name, str(i))
            self.emit_text("// -------- shader %s --------" % (combined_snip_name))
            shader_code = snip.code
            shader_code = shader_code.replace('main', combined_snip_name + '_main')
            shader_code = shader_code.replace('INPUT', combined_snip_name + '_INPUT')
            for samp in snip.samplers:
                shader_code = shader_code.replace(samp, combined_snip_name + '_' + samp)
            self.emit_text(shader_code)

    def emit_entrypoint(self, snips):
        self.emit_text("""// -------- entrypoint --------
OUTPUT main(const INPUT i)
{
    gInput = i;

    OUTPUT output = (OUTPUT)0;
""")

        for i, snip in enumerate(snips):
            combined_snip_name = self.make_name(snip.name, str(i))
            self.emit_text("    " + combined_snip_name + '_main(gInput.' + combined_snip_name + ', output);')

        self.emit_text("""
    return output;
}""")
